// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Instrument.cs" company="Open Trader">
//   Copyright (c) David Denis (david.denis@systemathics.com)
// </copyright>
// <summary>
//   |  Open Trader - The Open Source Systematic Trading Platform
//   |
//   |  This program is free software: you can redistribute it and/or modify
//   |  it under the terms of the GNU General Public License as published by
//   |  the Free Software Foundation, either version 2 of the License, or
//   |  (at your option) any later version.
//   |
//   |  This program is distributed in the hope that it will be useful,
//   |  but WITHOUT ANY WARRANTY; without even the implied warranty of
//   |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   |  GNU General Public License for more details.
//   |
//   |  You should have received a copy of the GNU General Public License
//   |  along with this program.  If not, see http://www.gnu.org/licenses
//   |
//   |  Up to date informations about Open Trader can be found at :
//   |    http://opentrader.org
//   |    http://opentrader.codeplex.com
//   |
//   |  For professional services, please visit us at :
//   |    http://www.systemathics.com
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Org.OpenTrader.Framework.Forge
{
    #region Using Directives

    using System;

    using Org.OpenTrader.Framework.Enums;
    using Org.OpenTrader.Framework.Forge.DataCache;
    using Org.OpenTrader.Framework.Forge.Enums;
    using Org.OpenTrader.Framework.Forge.Events;
    using Org.OpenTrader.Framework.Forge.Interfaces;
    using Org.OpenTrader.Framework.LinqKit;

    #endregion

    /// <summary>
    /// The instrument.
    /// </summary>
    public partial class Instrument : IInstrument
    {
        #region Constants and Fields

        /// <summary>
        /// The FeedEventCB.
        /// </summary>
        private EventHandler<FeedEventArgs> feedHandler;

        /// <summary>
        /// Gets OrderTrackingEventCB.
        /// </summary>
        private EventHandler<OrderTrackingEventArgs> orderTrackingHandler;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="Instrument"/> class.
        /// </summary>
        /// <param name="instrumentStaticDataID">
        /// The instrument static data id.
        /// </param>
        /// <param name="referential">
        /// The referential.
        /// </param>
        public Instrument(Guid instrumentStaticDataID, IReferential referential)
        {
            this.InstrumentStaticDataID = instrumentStaticDataID;
            this.Referential = referential;

            // Local tracking
            this.OrderTracker = new OrderTracker(this, referential.OrderProvider);

            // Nook to received feed
            this.MarketBook = new MarketBook(5);

            // FieldCache to receive fields
            this.MarketFields = new MarketFieldCache();

            // Exposition aggregator
            this.Expo = new InstrumentExpo(this);
        }

        #endregion

        #region Events

        /// <summary>
        /// The feed handler.
        /// </summary>
        public event EventHandler<FeedEventArgs> FeedEventCB
        {
            add
            {
                if (value == null)
                {
                    throw new NullReferenceException("FeedEventCB: += null");
                }

                // First time
                if (this.feedHandler == null)
                {
                    this.feedHandler = value;
                    var result = this.Referential.Subscribe(this.InstrumentStaticData.ID, this.OnFeed);
                    if (!result)
                    {
                        throw new Exception("this.Referential.Subscribe(this.InstrumentStaticData.ID, this.OnFeed);");
                    }

                    return;
                }

                // Following times
                this.feedHandler += value;
            }

            remove
            {
                if (value == null)
                {
                    throw new NullReferenceException("OrderTrackingEventCB: -= null");
                }

                // Remove
                this.feedHandler -= value;

                // No more remaining
                if (this.feedHandler == null)
                {
                    var result = this.Referential.Unsubscribe(this.InstrumentStaticData.ID, this.OnFeed);
                    if (!result)
                    {
                        throw new Exception("this.Referential.Subscribe(this.InstrumentStaticData.ID, this.OnFeed);");
                    }

                    return;
                }
            }
        }

        /// <summary>
        /// The order tracking handler.
        /// </summary>
        public event EventHandler<OrderTrackingEventArgs> OrderTrackingEventCB
        {
            add
            {
                if (value == null)
                {
                    throw new NullReferenceException("OrderTrackingEventCB: += null");
                }

                // First time
                if (this.orderTrackingHandler == null)
                {
                    this.orderTrackingHandler = value;
                    this.OrderTracker.EventCB += this.OnOrderTracking;
                    return;
                }

                // Following times
                this.orderTrackingHandler += value;
            }

            remove
            {
                if (value == null)
                {
                    throw new NullReferenceException("OrderTrackingEventCB: -= null");
                }

                // Remove
                this.orderTrackingHandler -= value;

                // No more remaining
                if (this.orderTrackingHandler == null)
                {
                    this.OrderTracker.EventCB -= this.OnOrderTracking;
                    return;
                }
            }
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets Expo.
        /// </summary>
        public IExpo<IInstrumentExpoCell> Expo { get; private set; }

        /// <summary>
        /// Gets or sets InstrumentStaticData.
        /// </summary>
        public IInstrumentStaticData InstrumentStaticData
        {
            get
            {
                return Singleton<DC>.Instance.GetInstrumentStaticDataFromCache(this.InstrumentStaticDataID);
            }
        }

        /// <summary>
        /// Gets InstrumentStaticDataID.
        /// </summary>
        public Guid InstrumentStaticDataID { get; private set; }

        /// <summary>
        /// Gets MarketBook.
        /// </summary>
        public IMarketBook MarketBook { get; private set; }

        /// <summary>
        /// Gets MarketFields.
        /// </summary>
        public IMarketFieldCache MarketFields { get; private set; }

        /// <summary>
        /// Gets OrderTracker.
        /// </summary>
        public IOrderTracker OrderTracker { get; private set; }

        /// <summary>
        /// Gets FactorPrice
        /// </summary>
        public double FactorPrice
        {
            get
            {
                if (this.InstrumentStaticData == null)
                {
                    return 0.0;
                }

                if (!this.InstrumentStaticData.FactorPrice.HasValue)
                {
                    return 0.0;
                }

                return this.InstrumentStaticData.FactorPrice.Value;
            }
        }
        
        /// <summary>
        /// Gets or sets Referential.
        /// </summary>
        private IReferential Referential { get; set; }

        #endregion

        #region Public Methods

        /// <summary>
        /// The to string.
        /// </summary>
        /// <returns>
        /// The string representation of this intrument
        /// </returns>
        public override string ToString()
        {
            ////        return string.Format(
            ////"Instrument<{0}>@{1}: {2} [{3}] [{4}]",
            ////this.InstrumentStaticData.GetType().FullName.Split(new char[] { '.' }).Last(),
            ////this.GetHashCode(),
            ////this.InstrumentStaticData.Code,
            ////this.InstrumentStaticData.TradingScope,
            ////this.InstrumentStaticData.ID);
            return string.Format("Instrument@{0}: {1} [{2}]", this.GetHashCode(), this.InstrumentStaticData.Code, this.InstrumentStaticData.TradingScope);
        }

        #endregion

        #region Implemented Interfaces

        #region IInstrument

        /// <summary>
        /// The buy.
        /// </summary>
        /// <param name="type">
        /// The type.
        /// </param>
        /// <param name="price">
        /// The price.
        /// </param>
        /// <param name="quantity">
        /// The quantity.
        /// </param>
        /// <param name="bookingPath">
        /// The bookingPath.
        /// </param>
        /// <returns>
        /// </returns>
        /// <exception cref="NotImplementedException">
        /// </exception>
        public Guid[] Buy(EOrderType type, double price, int quantity, string bookingPath)
        {
            // Create order
            var createstatus = this.OrderTracker.CreateOrder(type, EOrderWay.Buy, price, quantity, bookingPath);

            if (createstatus)
            {
                var order = createstatus.NestedObject;

                // Trading Security
                var tradingsecurity = SecurityChecker.CheckOrderSendRequest(this, order);

                if (tradingsecurity)
                {
                    var sendstatus = this.OrderTracker.SendOrder(order);
                    if (sendstatus)
                    {
                        return new Guid[] { order.ID };
                    }
                }
            }

            return new Guid[] { };
        }

        /// <summary>
        /// The cancel.
        /// </summary>
        /// <param name="id">
        /// The id.
        /// </param>
        /// <returns>
        /// The cancel.
        /// </returns>
        /// <exception cref="NotImplementedException">
        /// </exception>
        public bool Cancel(Guid id)
        {
            // Get order
            var cell = this.OrderTracker[id];
            if (cell == null)
            {
                return false;
            }

            // Trading Security
            var tradingsecurity = SecurityChecker.CheckOrderCancelRequest(this, cell.Order);
            if (!tradingsecurity)
            {
                return false;
            }

            if (cell.Pending)
            {
                return this.OrderTracker.CancelOrder(cell.Order);
            }
            else if (cell.Open)
            {
                return this.OrderTracker.CancelOrder(cell.Order);
            }

            return false;
        }

        /// <summary>
        /// The cancel.
        /// </summary>
        /// <param name="bookingPath">
        /// The bookingPath.
        /// </param>
        /// <returns>
        /// The cancel.
        /// </returns>
        /// <exception cref="NotImplementedException">
        /// </exception>
        public int Cancel(string bookingPath)
        {
            // "True"
            var predicate = PredicateBuilder.True<IOrderTrackingCell>();

            // "True" And "order is open"
            predicate = predicate.And(cell => cell.Open);

            // "True" And "order is open" and "Booking path starts with bookingPath"
            predicate = predicate.And(cell => cell.Order.BookingPath.StartsWith(bookingPath));

            var cells = this.OrderTracker.GetTrackingCells(predicate);

            var n = 0;
            foreach (var cell in cells)
            {
                n += this.OrderTracker.CancelOrder(cell.Order) ? 1 : 0;
            }

            return n;
        }

        /// <summary>
        /// The cancel all.
        /// </summary>
        /// <returns>
        /// The cancel all.
        /// </returns>
        /// <exception cref="NotImplementedException">
        /// </exception>
        public int CancelAll()
        {
            // "True"
            var predicate = PredicateBuilder.True<IOrderTrackingCell>();

            // "True" And "order is open"
            predicate = predicate.And(cell => cell.Open);

            var cells = this.OrderTracker.GetTrackingCells(predicate);

            var n = 0;
            foreach (var cell in cells)
            {
                n += this.OrderTracker.CancelOrder(cell.Order) ? 1 : 0;
            }

            return n;
        }

        /// <summary>
        /// The cancel all buy.
        /// </summary>
        /// <returns>
        /// The cancel all buy.
        /// </returns>
        /// <exception cref="NotImplementedException">
        /// </exception>
        public int CancelAllBuy()
        {
            // "True"
            var predicate = PredicateBuilder.True<IOrderTrackingCell>();

            // "True" And "order is open"
            predicate = predicate.And(cell => cell.Open);

            // "True" And "order is open" and "order is buy"
            predicate = predicate.And(cell => cell.Order.Way == EOrderWay.Buy);

            var cells = this.OrderTracker.GetTrackingCells(predicate);

            var n = 0;
            foreach (var cell in cells)
            {
                n += this.OrderTracker.CancelOrder(cell.Order) ? 1 : 0;
            }

            return n;
        }

        /// <summary>
        /// The cancel all sell.
        /// </summary>
        /// <returns>
        /// The cancel all sell.
        /// </returns>
        /// <exception cref="NotImplementedException">
        /// </exception>
        public int CancelAllSell()
        {
            // "True"
            var predicate = PredicateBuilder.True<IOrderTrackingCell>();

            // "True" And "order is open"
            predicate = predicate.And(cell => cell.Open);

            // "True" And "order is open" and "order is sell"
            predicate = predicate.And(cell => cell.Order.Way == EOrderWay.Sell);
            var cells = this.OrderTracker.GetTrackingCells(predicate);

            var n = 0;
            foreach (var cell in cells)
            {
                n += this.OrderTracker.CancelOrder(cell.Order) ? 1 : 0;
            }

            return n;
        }

        /// <summary>
        /// The modify.
        /// </summary>
        /// <param name="id">
        /// The id.
        /// </param>
        /// <param name="price">
        /// The price.
        /// </param>
        /// <param name="quantity">
        /// The quantity.
        /// </param>
        /// <param name="bookingPath">
        /// The bookingPath.
        /// </param>
        /// <returns>
        /// The modify.
        /// </returns>
        /// <exception cref="NotImplementedException">
        /// </exception>
        public bool Modify(Guid id, double price, int quantity, string bookingPath)
        {
            // Get order
            var cell = this.OrderTracker[id];
            if (cell == null)
            {
                return false;
            }

            if (cell.Pending || (cell.Open && !cell.Modifying))
            {
                var neworder = new Order(cell.Order.OrderProviderID, cell.Order.InstrumentID, cell.Order.Type, cell.Order.Way, price, quantity, bookingPath);

                // Trading Security
                var tradingsecurity = SecurityChecker.CheckOrderModifyRequest(this, cell.Order, neworder);
                if (tradingsecurity)
                {
                    return this.OrderTracker.ModifyOrder(cell.Order, neworder);
                }
                else
                {
                    return false;
                }
            }

            return false;
        }

        /// <summary>
        /// The sell.
        /// </summary>
        /// <param name="type">
        /// The type.
        /// </param>
        /// <param name="price">
        /// The price.
        /// </param>
        /// <param name="quantity">
        /// The quantity.
        /// </param>
        /// <param name="bookingPath">
        /// The bookingPath.
        /// </param>
        /// <returns>
        /// </returns>
        /// <exception cref="NotImplementedException">
        /// </exception>
        public Guid[] Sell(EOrderType type, double price, int quantity, string bookingPath)
        {
            // Create order
            var createstatus = this.OrderTracker.CreateOrder(type, EOrderWay.Sell, price, quantity, bookingPath);

            if (createstatus)
            {
                var order = createstatus.NestedObject;

                // Trading Security
                var tradingsecurity = SecurityChecker.CheckOrderSendRequest(this, order);

                if (tradingsecurity)
                {
                    var sendstatus = this.OrderTracker.SendOrder(order);
                    if (sendstatus)
                    {
                        return new Guid[] { order.ID };
                    }
                }
            }

            return new Guid[] { };
        }

        /// <summary>
        /// The shutdown.
        /// </summary>
        public void Shutdown()
        {
            // Force shutdown of order tracking events
            this.OrderTracker.EventCB -= this.OnOrderTracking;

            // Forge shutdown of feed subscription
            this.Referential.Unsubscribe(this.InstrumentStaticData.ID, this.OnFeed);

            // Reset listeners
            this.orderTrackingHandler = null;

            // Reset listeners
            this.feedHandler = null;
        }

        #endregion

        #endregion

        #region Methods

        /// <summary>
        /// Safely forward feed to caller, we catch exceptions
        /// </summary>
        /// <param name="sender">
        /// </param>
        /// <param name="args">
        /// </param>
        private void OnFeed(object sender, FeedEventArgs args)
        {
            if (this.feedHandler != null)
            {
                try
                {
                    if (args is FeedEventArgs.MarketBookUpdate)
                    {
                        var sargs = args as FeedEventArgs.MarketBookUpdate;
                        this.MarketBook.HandleUpdate(sargs);
                    }
                    else if (args is FeedEventArgs.MarketFieldUpdate)
                    {
                        var fargs = args as FeedEventArgs.MarketFieldUpdate;
                        this.MarketFields.HandleUpdate(fargs);
                    }

                    this.feedHandler(this, args);
                }
                catch (Exception)
                {
                }
            }
        }

        /// <summary>
        /// The on order tracking.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="args">
        /// The args.
        /// </param>
        private void OnOrderTracking(object sender, OrderTrackingEventArgs args)
        {
            // We're interested only in asynchronous tracking events (triggered on order events coming from outside) for Cells updating
            if (args is OrderTrackingEventArgs.AsynchronousUpdate)
            {
                // Forward orders events to non null exposition Cells with a non null OrderEventDelegate
                for (var i = 0; i < Enum.GetValues(typeof(EExpoCellType)).Length; i++)
                {
                    var instrumentExpoCell = this.Expo[(EExpoCellType)i];
                    if (instrumentExpoCell != null && instrumentExpoCell.OrderEventDelegate != null)
                    {
                        instrumentExpoCell.OrderEventDelegate(this, (args as OrderTrackingEventArgs.AsynchronousUpdate).RelatedOrderEventArgs);
                    }
                }
            }
            else if (args is OrderTrackingEventArgs.SynchronousUpdate)
            {
                // Forward orders events to non null exposition Cells with a non null OrderEventDelegate
                for (var i = 0; i < Enum.GetValues(typeof(EExpoCellType)).Length; i++)
                {
                    var instrumentExpoCell = this.Expo[(EExpoCellType)i];
                    if (instrumentExpoCell != null && instrumentExpoCell.OrderEventDelegate != null)
                    {
                        instrumentExpoCell.OrderEventDelegate(this, null);
                    }
                }
            }

            // Forward event securely to subscriber (if any)
            if (this.orderTrackingHandler != null)
            {
                try
                {
                    this.orderTrackingHandler(this, args);
                }
                catch (Exception)
                {
                }
            }
        }

        #endregion
    }
}